﻿using System;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using SilverPop.UI.Domain;

namespace SilverPop.UI.Controls
{
	[TemplatePart(Name="ItemsContainer", Type=typeof(Canvas))]
	public class DiagramCanvas : Control, INotifyPropertyChanged
	{
		public static readonly DependencyProperty ClickCanvasCommandProperty = DependencyProperty.Register(
			"ClickCanvas", typeof(ICommand), typeof(DiagramCanvas), null);

		public static readonly DependencyProperty DeleteNodeCommandProperty = DependencyProperty.Register(
			"DeleteNode", typeof(ICommand), typeof(DiagramCanvas), null);

		public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(
			"ItemsSource", typeof(IEnumerable), typeof(DiagramCanvas), new PropertyMetadata(OnItemSourceChanged));

		public static readonly DependencyProperty ItemTemplateProperty = DependencyProperty.Register(
			"ItemTemplate", typeof(DataTemplate), typeof(DiagramCanvas), null);

		public static readonly DependencyProperty AdornerTemplateProperty = DependencyProperty.Register(
			"AdornerTemplate", typeof(DataTemplate), typeof(DiagramCanvas), null);

		private static void OnItemSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			DiagramCanvas targetCanvas = d as DiagramCanvas;
			if (targetCanvas != null)
			{
				INotifyCollectionChanged oldData = e.OldValue as INotifyCollectionChanged;
				INotifyCollectionChanged newData = e.NewValue as INotifyCollectionChanged;
				targetCanvas.ItemSourceChanged(oldData, newData);
			}
		}

		
		/// <summary>
		/// Triggered when the ItemSource changes - detach from the old collection's collection
		/// changed event and attach to the new one.
		/// </summary>
		/// <param name="oldData"></param>
		/// <param name="newData"></param>
		private void ItemSourceChanged(INotifyCollectionChanged oldData, INotifyCollectionChanged newData)
		{
			if (oldData != null) 
				oldData.CollectionChanged -= AddOrRemoveNode;

			newData.CollectionChanged += AddOrRemoveNode;
		}

		/// <summary>
		/// Processes notified changes about the collection - IE: Item being added or removed.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void AddOrRemoveNode(object sender, NotifyCollectionChangedEventArgs e)
		{
			switch (e.Action)
			{
				case NotifyCollectionChangedAction.Add:
					foreach (object o in e.NewItems)
						AddNode(o);
					break;

				case NotifyCollectionChangedAction.Remove:
					foreach (object o in e.OldItems)
					{
						// Query child items that point to this entity
						object o1 = o;	// Prevent access to modified closure warning.
						FrameworkElement [] removals = ItemsContainer.Children
							.OfType<FrameworkElement>()
							.Where( i => i.DataContext == o1 ).ToArray();
						
						// Remove entities, removing selection and adorner as necessary
						foreach( FrameworkElement remove in removals )
						{
							if (remove == SelectedEntity) SelectedEntity = null;
							if (remove == _adorner) _adorner = null;
							ItemsContainer.Children.Remove(remove);
						}
					}
					break;
				case NotifyCollectionChangedAction.Replace:
				case NotifyCollectionChangedAction.Reset:
					break;
			}
		}

		/// <summary>
		/// The data source - the list of (in this case) notes.
		/// </summary>
		public IEnumerable ItemsSource
		{
			get
			{
				return GetValue(ItemsSourceProperty) as IEnumerable;
			}
			set
			{
				SetValue(ItemsSourceProperty, value);

				if (ItemsContainer != null)
				{
					ItemsContainer.Children.Clear();
					foreach (object o in ItemsSource)
					{
						AddNode(o);
					}
				}
			}
		}

		/// <summary>
		/// The ICommand to execute when the canvas is double clicked
		/// </summary>
		public ICommand ClickCanvasCommand
		{
			get
			{
				return GetValue(ClickCanvasCommandProperty) as ICommand;
			}
			set
			{
				SetValue(ClickCanvasCommandProperty, value);
			}
		}

		/// <summary>
		/// The ICommand to execute when the user hits the X button to delete a node
		/// </summary>
		public ICommand DeleteNodeCommand
		{
			get
			{
				return GetValue(DeleteNodeCommandProperty) as ICommand;
			}
			set
			{
				SetValue(DeleteNodeCommandProperty, value);
			}
		}

		/// <summary>
		/// Adds a node to the canvas, loading it's template from the xaml.
		/// </summary>
		/// <param name="context"></param>
		private void AddNode(object context)
		{
			FrameworkElement controlTree = ItemTemplate.LoadContent() as FrameworkElement;
			if (controlTree != null)
			{
				controlTree.DataContext = context;
				ItemsContainer.Children.Add(controlTree);

				controlTree.MouseLeftButtonDown += SelectEntityAndBeginDrag;
				controlTree.MouseLeftButtonUp += EntityEndDrag;
				controlTree.MouseMove += EntityDrag;
			}
		}

		/// <summary>
		/// The item template for standard items
		/// </summary>
		public DataTemplate ItemTemplate
		{
			get
			{
				return GetValue(ItemTemplateProperty) as DataTemplate;
			}
			set
			{
				SetValue(ItemTemplateProperty, value);
			}
		}

		/// <summary>
		/// The item template for an item that is selected
		/// </summary>
		public DataTemplate AdornerTemplate
		{
			get
			{
				return GetValue(AdornerTemplateProperty) as DataTemplate;
			}
			set
			{
				SetValue(AdornerTemplateProperty, value);
			}
		}

		// These fields hold references to controls within the control template.
		private Canvas ItemsContainer { get; set; }
		private Canvas PanContainer { get; set; }
		private TranslateTransform ContainerTransform { get; set; }
		private ScaleTransform ContainerScale { get; set; }
		private Button Extents { get; set; }

		/// <summary>
		/// Apply the control template
		/// </summary>
		public override void OnApplyTemplate()
		{
			ItemsContainer = GetTemplateChild("ItemsContainer") as Canvas;
			if (ItemsContainer == null)
				throw new InvalidOperationException("Invalid template");

			ContainerTransform = GetTemplateChild("ItemsContainerTranslateTransform") as TranslateTransform;
			if (ContainerTransform == null)
				throw new InvalidOperationException("Invalid template - needs translate transform to support drag");

			ContainerScale = GetTemplateChild("ItemsContainerScaleTransform") as ScaleTransform;
			if (ContainerScale == null)
				throw new InvalidOperationException("Invalid template - needs scale transform to support zoom");

			ItemsContainer.Children.Clear();
			foreach (object o in ItemsSource)
			{
				AddNode(o);
			}

			Extents = GetTemplateChild("ButtonExtents") as Button;
			if( Extents != null )
				Extents.Click += ExtentsClick;

			PanContainer = GetTemplateChild("PanContainer") as Canvas;
			if (PanContainer == null)
				throw new InvalidOperationException("Invalid template");

			PanContainer.MouseLeftButtonDown += OnPanStart;
			PanContainer.MouseLeftButtonUp += OnPanEnd;
			PanContainer.MouseMove += OnPanMove;

			var diagramContainerHost = GetTemplateChild("DiagramContainerHost") as FrameworkElement;
			/* Here, if the diagram container doesn't have clipping, we add clipping to the container whenever it's resized */
			if (diagramContainerHost != null)
			{
				if (diagramContainerHost.Clip == null)
				{
					diagramContainerHost.SizeChanged += (s, e) =>
					{
						FrameworkElement source = s as FrameworkElement;
						if (source != null)
						{
							source.Clip = new RectangleGeometry
							{
								Rect = new Rect(0, 0, source.ActualWidth, source.ActualHeight)
			 				};
						}
					};
				}
			}
		}

		/// <summary>
		/// Handler for the user clicking the zoom to extents button on the zoom panel.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void ExtentsClick(object sender, RoutedEventArgs e)
		{	
			Rect totalSize = GetElementBoundsSize();
			totalSize.X += 230;			// Remove the boundary margins - we want to zoom
			totalSize.Y += 230;			// to extents of the diagram elements not zoom
			totalSize.Width -= 460;		// to the extents of the diagram + it's draggable margin.
			totalSize.Height -= 460;

			//Rect viewportSize = new Rect(0, 0, this.RenderSize.Width, this.RenderSize.Height);
			// Hmmm - viewport size using my actualwidth, rendersize etc doesn't give me the actual area available
			// as it doesn't take into account margins and padding.
			// Instead let's use the pancontainer which should be sized to what we have available....
			Rect viewportSize = new Rect(0, 0, PanContainer.ActualWidth, PanContainer.ActualHeight);

			// Do a little math to work out the zoom level to fit everything on the canvas.
			double widthZoom = viewportSize.Width / totalSize.Width;
			double heightZoom = viewportSize.Height / totalSize.Height;
			double fitZoom = Math.Min(widthZoom, heightZoom);

			// Update the scale and set the transform to the top left position.
			ContainerScale.SetValue(ScaleTransform.ScaleXProperty, fitZoom);
			ContainerScale.SetValue(ScaleTransform.ScaleYProperty, fitZoom);
			ContainerTransform.SetValue(TranslateTransform.XProperty, -totalSize.Left);
			ContainerTransform.SetValue(TranslateTransform.YProperty, -totalSize.Top);

		}

		/// <remarks/>
		public DiagramCanvas()
		{
			DefaultStyleKey = typeof(DiagramCanvas);
		}

		// These fields are used to control panning, timing of double clicks
		// work out which entity is selected, being dragged etc.
		private bool _isPanning;
		private Point _startPosition;
		private double _downA;
		private double _downB;
		private bool _isEntityDrag;
		private FrameworkElement _selectedEntity;
		private FrameworkElement _adorner;
		bool _rotateSelected;
		bool _resizing;
		Point _lastPosition;
		bool _firstSample = true;
		Rect _currentBounds;

		/// <summary>
		/// Exposes the currently selected entity.
		/// </summary>
		public FrameworkElement SelectedEntity
		{
			get { return _selectedEntity; }
			set { _selectedEntity = value; DoPropertyChanged("SelectedEntity"); }
		}
		
		/// <summary>
		/// Handles mouse down on an entity in the diagram - selects it, adorns it with the 
		/// resize handles, rotator and text editor and begins a drag operation.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void SelectEntityAndBeginDrag(object sender, MouseButtonEventArgs e)
		{
			SelectedEntity = sender as FrameworkElement;
			if (SelectedEntity == null) return;

			AdornSelectedEntity();

			// Get where abouts on the entity we clicked (this will be used later)
			_startPosition = e.GetPosition(SelectedEntity);
			_isEntityDrag = true;
			SelectedEntity.CaptureMouse();
		}

		/// <summary>
		/// Completes an entity drag
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void EntityEndDrag(object sender, MouseButtonEventArgs e)
		{
			if (SelectedEntity != null)
			{
				SelectedEntity.ReleaseMouseCapture();
				_isEntityDrag = false;
			}
		}

		/// <summary>
		/// Creates an adorner version of the selected entity and pushes it to the top of the Z-Order
		/// on the canvas for maniuplation.
		/// </summary>
		private void AdornSelectedEntity()
		{
			// If we already have an adorner, deselect if it's not on the current entity.
			if( _adorner != null )
			{
				if( SelectedEntity == _adorner ) return;
				_adorner.DataContext = null;
				ItemsContainer.Children.Remove(_adorner);
				_adorner = null;
			}

			if( SelectedEntity == null ) return;
			var context = SelectedEntity.DataContext;
			if( context == null ) return;

			FrameworkElement controlTree = AdornerTemplate.LoadContent() as FrameworkElement;
			if (controlTree != null)
			{
				controlTree.DataContext = context;
				ItemsContainer.Children.Add(controlTree);

				// Get the various components in the data template
				Thumb tlThumb = GetDataTemplateChild<Thumb>(controlTree, "ThumbTL");
				Thumb trThumb = GetDataTemplateChild<Thumb>(controlTree, "ThumbTR");
				Thumb blThumb = GetDataTemplateChild<Thumb>(controlTree, "ThumbBL");
				Thumb brThumb = GetDataTemplateChild<Thumb>(controlTree, "ThumbBR");
				Thumb rotThumb = GetDataTemplateChild<Thumb>(controlTree, "ThumbROT");
				Button deleteButton = GetDataTemplateChild<Button>(controlTree, "DeleteButton");
				
				// Hook events
				deleteButton.Click += (s, e) =>
				{
					if (DeleteNodeCommand != null)
					{
						if (SelectedEntity != null)
							DeleteNodeCommand.Execute(SelectedEntity.DataContext);
					}
				};


				tlThumb.Tag = Thumbs.TopLeft;
				trThumb.Tag = Thumbs.TopRight;
				blThumb.Tag = Thumbs.BottomLeft;
				brThumb.Tag = Thumbs.BottomRight;

				tlThumb.MouseEnter += SetResizeCursor;
				trThumb.MouseEnter += SetResizeCursor;
				blThumb.MouseEnter += SetResizeCursor;
				brThumb.MouseEnter += SetResizeCursor;
				rotThumb.MouseEnter += SetRotateCursor;

				tlThumb.MouseLeave += ResetCursor;
				trThumb.MouseLeave += ResetCursor;
				blThumb.MouseLeave += ResetCursor;
				brThumb.MouseLeave += ResetCursor;
				rotThumb.MouseLeave += ResetCursor;

				rotThumb.DragStarted += BeginRotateThumb;
				rotThumb.DragCompleted += CompleteRotateThumb;
				rotThumb.MouseMove += DragRotThumb;

				tlThumb.DragStarted += BeginDragSizeThumb;
				tlThumb.DragCompleted += CompleteDragSizeThumb;
				tlThumb.MouseMove += DragSizeThumb;

				trThumb.DragStarted += BeginDragSizeThumb;
				trThumb.DragCompleted += CompleteDragSizeThumb;
				trThumb.MouseMove += DragSizeThumb;

				blThumb.DragStarted += BeginDragSizeThumb;
				blThumb.DragCompleted += CompleteDragSizeThumb;
				blThumb.MouseMove += DragSizeThumb;

				brThumb.DragStarted += BeginDragSizeThumb;
				brThumb.DragCompleted += CompleteDragSizeThumb;
				brThumb.MouseMove += DragSizeThumb;

				controlTree.MouseLeftButtonDown += SelectEntityAndBeginDrag;
				controlTree.MouseLeftButtonUp += EntityEndDrag;
				controlTree.MouseMove += EntityDrag;
			}
			_adorner = controlTree;
		}

		/// <summary>
		/// When user drags the rotate handle, we need to rotate the entity to the angle reflected by the
		/// current mouse position versus the centre of the entity.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void DragRotThumb(object sender, MouseEventArgs e)
		{
			if( ! _rotateSelected ) return;

			double w2 = SelectedEntity.Width / 2;
			double h2 = SelectedEntity.Height / 2;
			double x = (double) SelectedEntity.GetValue(Canvas.LeftProperty);
			double y = (double)SelectedEntity.GetValue(Canvas.TopProperty);
			Point centreOfRotation = new Point(x + w2, y + h2);

			Point currentPosition = e.GetPosition(ItemsContainer);
			currentPosition.X -= centreOfRotation.X;
			currentPosition.Y -= centreOfRotation.Y;

			double angle = (Math.Atan2(currentPosition.Y, currentPosition.X) * 180) / Math.PI;

			Note n = SelectedEntity.DataContext as Note;
			// Add 90 due to originating angle of the resize handle being 90 degrees to the vertical.
			if (n != null) 
				n.Angle = angle + 90;
		}

		/// <summary>
		/// Stop rotation dragging
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void CompleteRotateThumb(object sender, DragCompletedEventArgs e)
		{
			_rotateSelected = false;
		}

		/// <summary>
		/// Begin rotation dragging
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void BeginRotateThumb(object sender, DragStartedEventArgs e)
		{
			_rotateSelected = true;
		}
		
		/// <summary>
		/// Triggered when mouse moves over the rotate handle - change cursor to hand.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void SetRotateCursor(object sender, MouseEventArgs e)
		{
			Cursor = Cursors.Hand;
		}

		/// <summary>
		/// Triggered when mouse moves over a resize handle - set cursor accordingly.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="args"></param>
		private void SetResizeCursor(object sender, MouseEventArgs args)
		{
			Thumb t = sender as Thumb;
			if (t == null) return;
			Thumbs thumb = (Thumbs) t.Tag;

			if( thumb == Thumbs.TopLeft || thumb == Thumbs.BottomRight )
				Cursor = Cursors.SizeNWSE;
			if( thumb == Thumbs.TopRight || thumb == Thumbs.BottomLeft )
				Cursor = Cursors.SizeNESW;
		}

		/// <summary>
		/// Mouse moves out of a resize/rotation handle - reset to arrow cursor.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="args"></param>
		private void ResetCursor(object sender, MouseEventArgs args)
		{
			Cursor = Cursors.Arrow;
		}

		/// <summary>
		/// Start dragging a resize handle
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void BeginDragSizeThumb(object sender, DragStartedEventArgs e)
		{
			Thumb t = sender as Thumb;
			if( t != null )
			{
				_resizing = true;		
				_lastPosition = new Point(0,0);
				_firstSample = true;
			}
		}

		/// <summary>
		/// Finish dragging a resize handle
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void CompleteDragSizeThumb(object sender, DragCompletedEventArgs e)
		{
			_resizing = false;
		}

		/// <summary>
		/// Resize handle being moved, calculate amount of movement since last sample and update
		/// the entity to reflect this size change.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="args"></param>
		private void DragSizeThumb(object sender, MouseEventArgs args)
		{
			if( ! _resizing ) return;

			Thumb t = sender as Thumb;
			if (t == null) return;
			Thumbs tt = (Thumbs) t.Tag;
			
			// Work out the X/Y delta
			Point p = args.GetPosition(this);
			if( _firstSample )
			{
				_lastPosition = p;
				_firstSample = false;
			}

			Point delta = new Point(_lastPosition.X - p.X, _lastPosition.Y - p.Y);
			// Offset delta by zoom factor
			delta.X /= (double)ContainerScale.GetValue(ScaleTransform.ScaleXProperty);
			delta.Y /= (double)ContainerScale.GetValue(ScaleTransform.ScaleXProperty);

			_lastPosition = p;

			Rect entPosition = new Rect(
				(double) SelectedEntity.GetValue(Canvas.LeftProperty),
				(double) SelectedEntity.GetValue(Canvas.TopProperty),
				(double) SelectedEntity.GetValue(WidthProperty),
				(double) SelectedEntity.GetValue(HeightProperty));

			entPosition = entPosition.OffsetSafe(tt, delta);

			if( entPosition.Width > 0 )
			{
				SelectedEntity.SetValue(Canvas.LeftProperty, entPosition.X);
				SelectedEntity.SetValue(WidthProperty, entPosition.Width);
			}
			if( entPosition.Height > 0 )
			{
				SelectedEntity.SetValue(Canvas.TopProperty, entPosition.Y);
				SelectedEntity.SetValue(HeightProperty, entPosition.Height);
			}
		}

		/// <summary>
		/// Helper method that will get a child within a data template. Doesn't really belong here, should be refactored out
		/// to a seperate class.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="target"></param>
		/// <param name="controlName"></param>
		/// <returns></returns>
		private T GetDataTemplateChild<T>(UIElement target, string controlName) where T : FrameworkElement
		{
			// determing if the target passed in is the type we're after and has the name required...
			var targetFrameworkElement = target as FrameworkElement;
			if( targetFrameworkElement != null && target.GetType()==typeof(T) && targetFrameworkElement.Name.Equals(controlName))
			{
				return targetFrameworkElement as T;
			}

			for( var i=0; i<VisualTreeHelper.GetChildrenCount(target); i++ )
			{
				UIElement child = VisualTreeHelper.GetChild( target, i ) as UIElement;
				if( child != null )
				{
					T result = GetDataTemplateChild<T>(child, controlName);
					if (result != null) return result;
				}
			}

			return null;
		}

		/// <summary>
		/// Entity is being dragged around the canvas, update it's position.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void EntityDrag(object sender, MouseEventArgs e)
		{
			if (SelectedEntity != null && _isEntityDrag)
			{
				// Get the current position relative to the canvas it's contained on
				var currentPosition = e.GetPosition(ItemsContainer);
				
				// Offset the current position with where we clicked on the original entity
				var delta = new Point(currentPosition.X - _startPosition.X, currentPosition.Y - _startPosition.Y);
				SelectedEntity.SetValue(Canvas.LeftProperty, delta.X);
				SelectedEntity.SetValue(Canvas.TopProperty, delta.Y);
			}
		}

		
		/// <summary>
		/// Mouse down on the canvas - it's perhaps about to be panned, or we're clicking 
		/// the canvas and need to time the double click speed.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void OnPanStart(object sender, MouseButtonEventArgs e)
		{
			// De-select entity and call adorn code to remove adornments
			SelectedEntity = null;	
			AdornSelectedEntity();

			_isPanning = true;
			_startPosition = e.GetPosition(this);
			PanContainer.CaptureMouse();
			_currentBounds = GetElementBoundsSize();
		}

		/// <summary>
		/// The number of ticks that 2 clicks must be actioned within to count as a double click.
		/// </summary>
		private const double DoubleClickSpeed = 2000000d;
		
		/// <summary>
		/// Mouse up on the canvas - stop panning but also check if we've carried out two
		/// clicks within the timeframe required to count as a double click. If it's a double click,
		/// we can invoke the ICommand bound to the control
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void OnPanEnd(object sender, MouseButtonEventArgs e)
		{
			_isPanning = false;
			PanContainer.ReleaseMouseCapture();

			if (_downA != 0)
			{
				_downB = DateTime.Now.Ticks;
				// See if it was too long between ticks - if it was, this second click will become the first;
				if ((_downB - _downA) > DoubleClickSpeed)
				{
					_downA = 0;
					_downB = 0;
				}
			}

			if (_downA == 0)
			{
				_downA = DateTime.Now.Ticks;
				_downB = 0;
			}
			else
			{
				if ((_downB - _downA) < DoubleClickSpeed)
				{
					if (ClickCanvasCommand != null)
					{
						// Get co-ordinates relative to the canvas
						Point location = e.GetPosition(ItemsContainer);
						ClickCanvasCommand.Execute(new MouseButtonCommandArguments
						                           {
						                           	Sender = this,
													Position = location
						                           });
					}
				}

				_downA = 0;
				_downB = 0;
			}			
		}

		/// <summary>
		/// Works out the boundary of all the entities on the cavnas.
		/// </summary>
		/// <returns></returns>
		protected Rect GetElementBoundsSize()
		{
			if (ItemsContainer == null) return new Rect(0, 0, 500, 500);
			if (ItemsContainer.Children.Count < 1) return new Rect(0, 0, 500, 500);
			Rect bounds = Rect.Empty;
			
			foreach (UIElement element in ItemsContainer.Children)
			{
				Rect elementPosition = new Rect
				{
					X = (double) element.GetValue(Canvas.LeftProperty),
					Y = (double) element.GetValue(Canvas.TopProperty),
					Width = element.RenderSize.Width,
					Height = element.RenderSize.Height
				};

				if (bounds == Rect.Empty) bounds = elementPosition;
				else
					bounds.Union(elementPosition);
			}

			// Expand bounds with available border
			bounds.X -= 250;
			bounds.Y -= 250;
			bounds.Width += 500;
			bounds.Height += 500;
			return bounds;
		}

		/// <summary>
		/// Mouse movement on the canvas - if we're panning, pan the canvas.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void OnPanMove(object sender, MouseEventArgs e)
		{
			if (_isPanning)
			{
				// Work out the delta between the last sample and the current position
				var currentPosition = e.GetPosition(this);
				var delta = new Point(currentPosition.X - _startPosition.X, currentPosition.Y - _startPosition.Y);

				// Transform delta according to zoom
				delta.X /= (double)ContainerScale.GetValue(ScaleTransform.ScaleXProperty);
				delta.Y /= (double)ContainerScale.GetValue(ScaleTransform.ScaleXProperty);
				
				// Reset last sample to new one ready for next cycle.
				_startPosition = currentPosition;

				// Calculate the newly transformed X and Y coordinates by offsetting the delta
				var newTransformX = ContainerTransform.X + delta.X;
				var newTransformY = ContainerTransform.Y + delta.Y;

				// Trim new transform into bounds of the element bounds - IE we can only drag the canvas to
				// pan within the constraints of the entities themselves + a gutter.
				if ((newTransformX * -1) > _currentBounds.Left && (newTransformX * -1) < _currentBounds.Right)
				{
					ContainerTransform.X = newTransformX;
				}

				if ((newTransformY * -1) > _currentBounds.Top && (newTransformY * -1) < _currentBounds.Bottom)
				{
					ContainerTransform.Y = newTransformY;
				}
			}
		}

		/// <summary>
		/// Fire property changed events
		/// </summary>
		/// <param name="property"></param>
		private void DoPropertyChanged(string property)
		{
			if( PropertyChanged != null )
				PropertyChanged(this, new PropertyChangedEventArgs(property));
		}
		
		/// <summary>
		/// 
		/// </summary>
		public event PropertyChangedEventHandler PropertyChanged;
	}
}
